#!/usr/local/bin/ruby
# Copyright (C) 2003-2010 Wang Pengcheng <wpc0000@gmail.com>
# , released under the MIT license
# You may obtain a template of the MIT License at
# http://www.opensource.org/licenses/mit-license.php
$KCODE = 'utf8'

class Array
  def / len
    a = []
    each_with_index do |x,i|
      a << [] if i % len == 0
      a.last << x
    end
    a
  end

  def sum
    inject( nil ) { |sum,x| sum ? sum+x : x }
  end

  def mean
    sum.to_f / size.to_f
  end

  # Calculate the Confidence interval percentage
  # :length the number of batches
  # :per the percentage
  def self.cip(data)
    m = data.mean
    tot = 0
    data.each do |i|
      tot += (i-m)**2
    end
    tot /= (data.length-1)
    n = 1.95996
    batch=data.length
    n = 2.04227 if batch <=30
    n = 2.05954 if batch <=25
    n = 2.08596 if batch <=20
    n = 2.13145 if batch <=15
    n = 2.22814 if batch <=10
    n = 2.57058 if batch <=5

    n = 2.77645  if batch <= 4
    n = 3.18245  if batch <= 3
    n = 4.30265  if batch <= 2
    n = 12.70620 if batch <= 1

    interval = tot**0.5/(batch**0.5)*n
    percentage = interval/m*100
    {:length=>batch,:per=>percentage,:interval => interval,:mean=>m, :data=>data}
  end

  # Get batch values
  def self.batch_size(value)
    value ||= 50
    value.to_i
  end
end

# Packet
class Packet
  attr_reader :length,:sources
  attr_accessor :timestamps

  def initialize(length, sources)
    @length = length.to_f
    @sources = sources.split(',').map {|i| i.to_i}
    @timestamps = nil
  end

  # The packet is lost or not
  def lost?
    path_length<@sources.length
  end

  # The length of real transfering path
  def path_length
    @timestamps.length
  end

  # The lost point of the sources
  def lost_point
    @sources[lost_point_index]
  end

  # The lost point index
  def lost_point_index
    path_length-1
  end

  PATH_SPIL = '->'

  # The aim of transfering path
  def path
    @sources.join(PATH_SPIL)
  end

  # The packet's delay time
  def delay
    time = @timestamps[0][0]-@timestamps[-1][0]
    time = -time if time<0
    time
  end

  QUEUE_SPIL ='-'

  # Find the arrival time at source si
  def arrival_i(si)
    # If si is string, find the integer si
    if (si.class==String)and(si.include?(QUEUE_SPIL))
      si = si.split(QUEUE_SPIL)[0].to_i
    end
    i = 0
    i+= 1 while @sources[i]!=si
    @timestamps[i][0]
  end

  # Get the queue id
  def get_queue(i)
    if i<@sources.length-1
      @sources[i].to_s+QUEUE_SPIL+@sources[i+1].to_s
    else
      @sources.last
    end
  end

  def self.map_by_path(packets)
    path_group = {}

    packets.each do |packet|
      path = packet.path
      path_group[path]=[] unless path_group[path]
      path_group[path] << packet
    end
    path_group
  end

  def self.mean_delay(packets)
    tot = 0
    packets.each do |i|
      tot += i.delay
    end
    tot*1.0/packets.length
  end

  def self.delay_time_results(fgroup)
    nwValues = []
    fgroup.each_pair do |key,value|
      ci = confidence_interval(value, BANTCH_SIZE) {|i| mean_delay(i)}
      print key, ': ', ci[:mean],'(Length:',value.length,')'
      print 'CIP(',ci[:length],'/',BANTCH_SIZE,'):',ci[:per]
      puts ''
      nwValues << {:path=>path_to_tex(key).to_s, :mean=>ci[:mean].to_s, :per=>ci[:per].to_s, :arrival_size=>value.length.to_s, :blength=>BANTCH_SIZE.to_s, :ci => ci}
    end
    nwValues
  end

  def self.loss_rate_results(rate_map, is_queue=true)
    nwLossRate =[]
    rate_map.each_pair do |key,value|

      packets = value[:packets]
      packets.sort! do |pi,pj|
        pi.arrival_i(key) <=> pj.arrival_i(key)
      end

      ci = confidence_interval(packets, BANTCH_SIZE) do |i|
        lost = 0
        i.each do |packet|
          unless is_queue
            lost += 1 if packet.lost? and packet.lost_point==key
          else
            lost += 1 if packet.lost? and packet.get_queue(packet.lost_point_index)==key
          end
        end
        lost*1.0/i.length
      end

      print key, ': ','All=',value[:all],', Lost=',value[:lost], ', Loss Rate=', ci[:mean]
      print ' CIP(',ci[:length],'/',BANTCH_SIZE,'):',ci[:per]
      puts ''
      nwLossRate << {:path=>path_to_tex(key).to_s, :mean=>ci[:mean].to_s, :per=>ci[:per].to_s, :arrival_size=>value[:all].to_s, :blength=>BANTCH_SIZE.to_s, :ci => ci}
    end
    nwLossRate
  end

  def self.loss_map(packets, is_queue=true)
    rate_map = {}
    packets.each do |packet|
      (0...packet.path_length).each do |i|
        if is_queue
          path = packet.get_queue(i)
        else
          path = packet.sources[i]
        end
        rate_map[path] = {:all=>0 ,:lost=>0, :packets=>[]} unless rate_map[path]
        rate_map[path][:all] += 1
        rate_map[path][:packets] << packet
      end
      if is_queue
        lost_path =packet.get_queue(packet.lost_point_index)
      else
        lost_path = packet.lost_point
      end
      rate_map[lost_path][:lost]+=1 if packet.lost?
    end
    rate_map
  end

  def self.loss_rate_to_router_map(packets)
    loss_map(packets, false)
  end

  def self.loss_rate_to_queue_map(packets)
    loss_map(packets, true)
  end

  # Split the packets to batches by batch_length
  # And calculate the confidence interval for batches
  def self.confidence_interval(packets, batch_length)
    packets = packets.reverse
    subsets = packets/batch_length

    values = []

    subsets.each do |set|
      if set.length == batch_length
        values << yield(set)
      end
    end
    Array.cip(values)
  end
end

class FilePacket
  UNDEFINED = -10

  def initialize(file)
    data=File.read(file)
    data=data.split("\n")
    init(data)
  end

  def finish_packets
    @fps
  end

  def lost_packets
    @lps
  end

  def all_packets
    finish_packets+lost_packets
  end

  def delay_results
    Packet.delay_time_results(@fmap)
  end

  def loss_rate_results(is_queue=true)
    all=all_packets
    if is_queue
     loss_map = Packet.loss_rate_to_queue_map(all)
    else
     loss_map = Packet.loss_rate_to_router_map(all)
    end
    Packet.loss_rate_results(loss_map, is_queue)
  end

  private
  def init(data)
    flposition(data)
    @fps = get_packets(@fb,@lb,data)
    @lps = get_packets(@lb,@data_length,data)
    @fmap = Packet.map_by_path(@fps)
  end

  def flposition(data)
    i = 0
    @fb = UNDEFINED
    @lb = UNDEFINED
    @data_length = data.length

    while i<@data_length
      @fb=i if @fb==UNDEFINED and data[i]=~/Finish Packet Details:/
        @lb=i if @lb==UNDEFINED and data[i]=~/Lost Packet Details:/
        i+=1
    end
  end

  def get_packets(bline,eline, data)
    res = []
    i = bline
    while i<eline
      if data[i]=~/Packet\(.+\):Length=(\d+\.?\d*),Sources=\[(.+)\],TimestampsLength=(\d+)/
        mdata = Regexp.last_match
        packet = Packet.new(mdata[1], mdata[2])
        j = mdata[3].to_i
        timestamps = []
        (1..j).each do |sti|
          data[i+sti]=~/A:(.+),W:(.+),C:(.+)/
            smdata = Regexp.last_match
          timestamps << smdata[1..3].map {|f| f.to_f}
        end
        packet.timestamps = timestamps
        res<<packet
      end
      i += 1
    end
    res
  end
end

def networkValues(values)
  res = ''
  values.each do |value|
    res << value[:path] << '&' << value[:mean] << '&' << value[:per] << '&'<< value[:arrival_size] << '&' << value[:blength] << "\\\\\n"
  end
  res
end

def path_to_tex(paths)
  #paths = paths.split(Packet::PATH_SPIL)
  paths
end

ArrivalSize = 1.25*2.45*10000*102
